package com.sheng.project.demo.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.archivers.*;
import org.apache.commons.compress.compressors.CompressorException;
import org.apache.commons.compress.compressors.CompressorInputStream;
import org.apache.commons.compress.compressors.CompressorOutputStream;
import org.apache.commons.compress.compressors.CompressorStreamFactory;
import org.apache.commons.compress.compressors.bzip2.BZip2Utils;
import org.apache.commons.compress.compressors.gzip.GzipUtils;
import org.apache.commons.compress.compressors.xz.XZUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

/**
 * @author by ls
 * @date 2025/10/16
 */
@Slf4j
public class FileTool {

    private static final String SIZE_MEASUREMENT_UNIT_B = "B";
    private static final String SIZE_MEASUREMENT_UNIT_K = "K";
    private static final String SIZE_MEASUREMENT_UNIT_M = "M";
    private static final String SIZE_MEASUREMENT_UNIT_G = "G";
    private static final String SIZE_MEASUREMENT_UNIT_T = "T";

    private static final long SIZE_RADIX_B = 1;
    private static final long SIZE_RADIX_K = SIZE_RADIX_B * 1024;
    private static final long SIZE_RADIX_M = SIZE_RADIX_K * 1024;
    private static final long SIZE_RADIX_G = SIZE_RADIX_M * 1024;
    private static final long SIZE_RADIX_T = SIZE_RADIX_G * 1024;

    // Linux/Unix 与 Windows 的目录具备差异 '/' or '\\'
    private static final String SLASH = "/";

    /*=========压缩文件 start=========*/

    public static void main(String[] args) throws IOException, ArchiveException {
        FileTool fileTool = new FileTool();
        String path1 = "D:/storage/tmp";
        String path2 = "D:/storage/2022-01-23/aaa.zip";
        File srcFile = FileUtils.getFile(path1);
        File destFile = FileUtils.getFile(path2);
        fileTool.compressDirectoryByArchiver(srcFile, destFile, ArchiveStreamFactory.ZIP);
    }

    /**
     * 下载网络文件
     *
     * @param urlPath   下载地址 URL
     * @param targetDir 保存文件路径
     * @return 下载的文件名
     */
    public static String downloadNet(String urlPath, String targetDir) throws Exception {
        URL url = new URL(urlPath);
        HttpURLConnection http = (HttpURLConnection) url.openConnection();
        http.setConnectTimeout(3000);
        http.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)");

        String fileName = getFileName(http, urlPath);
        InputStream inputStream = http.getInputStream();
        OutputStream out = Files.newOutputStream(new File(targetDir, fileName).toPath());

        int len;
        byte[] buff = new byte[1024 * 10];
        while ((len = inputStream.read(buff)) != -1) {
            out.write(buff, 0, len);
            out.flush();
        }

        out.close();
        inputStream.close();
        http.disconnect();
        return fileName;
    }

    /**
     * 解析链接中文件名
     *
     * @param http    http连接
     * @param urlPath 网络地址
     * @return 文件名
     */
    public static String getFileName(HttpURLConnection http, String urlPath) throws UnsupportedEncodingException {
        String headerField = http.getHeaderField("Content-Disposition");
        String fileName = null;
        if (headerField != null) {
            String decode = URLDecoder.decode(headerField, "UTF-8");
            fileName = decode.split(";")[1].split("=")[1].replaceAll("\"", "");
        }
        // 尝试从url中获取文件名
        if (fileName == null) {
            String[] arr = urlPath.split("/");
            fileName = arr[arr.length - 1];
        }
        return fileName;
    }

    /**
     * 解压 ZIP 文件
     * 解压目录为空则为解压到当前文件目录下
     *
     * @param zipPath   文件目录 例: (C:\Users\Administrator\20887210350164810156_20160405.csv.zip)
     * @param targetDir 解压目录 例: (C:\Users\Administrator) 解压目录为空则为解压到当前文件目录下
     */
    public static List<String> unZipFiles(String zipPath, String targetDir) {
        try {
            return unZipFiles(new File(zipPath), targetDir);
        } catch (IOException e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
    }

    /**
     * 解压ZIP文件
     *
     * @param zipFile   原始文件
     * @param targetDir 解压目录 例: (C:\Users\Administrator) 解压目录为空则为解压到当前文件目录下
     * @return 文件地址列表
     */
    public static List<String> unZipFiles(File zipFile, String targetDir) throws IOException {
        // 解决中文文件夹乱码
        ZipFile zip = new ZipFile(zipFile, Charset.forName("GBK"));

        // 获取压缩包文件名(此处 Linux/Unix 与 Windows 的目录具备差异 '/' or '\\')
        String zipName = zip.getName().substring(zip.getName().lastIndexOf(SLASH) + 1, zip.getName().lastIndexOf('.'));
        String zipPath = zip.getName().substring(0, zip.getName().lastIndexOf(SLASH));

        // 处理解压后文件最终保存路径
        targetDir = StringUtils.isEmpty(targetDir) ? zipPath + SLASH + zipName : targetDir + SLASH + zipName;
        // 列表第一位始终为解压根目录
        List<String> pathList = new ArrayList<>();
        pathList.add(targetDir);

        for (Enumeration<? extends ZipEntry> entries = zip.entries(); entries.hasMoreElements(); ) {
            ZipEntry entry = entries.nextElement();
            String zipEntryName = entry.getName();
            InputStream in = zip.getInputStream(entry);
            String outPath = (targetDir + SLASH + zipEntryName).replaceAll("\\*", SLASH);
            pathList.add(outPath);
            // 判断路径是否存在，不存在则创建文件路径
            File file = new File(outPath.substring(0, outPath.lastIndexOf(SLASH)));
            if (!file.exists()) {
                if (!file.mkdirs()) {
                    throw new RuntimeException("文件夹创建失败");
                }
            }
            // 判断文件全路径是否为文件夹，如果是上面已经上传，不需要解压，防止重复生成
            if (new File(outPath).isDirectory()) {
                continue;
            }
            // 输出文件路径信息
            FileOutputStream out = new FileOutputStream(outPath);
            byte[] buf1 = new byte[1024];
            int len;
            while ((len = in.read(buf1)) > 0) {
                out.write(buf1, 0, len);
            }
            in.close();
            out.close();
        }
        return pathList;
    }

    /**
     * 压缩文件（夹）
     *
     * @param srcDirAbsolutePath  源文件夹绝对路径
     * @param srcFileName         源文件名称
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param destFileName        目标文件名称
     * @param compressType        压缩类型：ar,arj,cpio,dump,jar,tar,zip;bzip2,gz,pack200,xz,lzma
     * @throws Exception
     */
    public void compress(String srcDirAbsolutePath, String srcFileName, String destDirAbsolutePath, String destFileName, String compressType) throws Exception {
        //ar,arj,cpio,dump,jar,tar,zip
        if (CompressorFactory.archiversMap.containsKey(compressType.toLowerCase(Locale.ENGLISH))) {
            compressArchiver(srcDirAbsolutePath, srcFileName, destDirAbsolutePath, destFileName, compressType);
        }
        //bzip2,gz,pack200,xz,lzma
        else if (CompressorFactory.compressorsMap.containsKey(compressType.toLowerCase(Locale.ENGLISH))) {
            compressCompressor(srcDirAbsolutePath, srcFileName, destDirAbsolutePath, destFileName, compressType);
        }
        //7z
        else if (CompressorFactory.othersMap.containsKey(compressType.toLowerCase(Locale.ENGLISH))) {
            throw new IllegalArgumentException("Compressor or archiver " + compressType + " not support.");
        } else {
            throw new IllegalArgumentException("Compressor or archiver " + compressType + " not found.");
        }
    }

    /**
     * 压缩文件（夹）
     *
     * @param srcDirAbsolutePath  源文件夹绝对路径
     * @param srcFileName         源文件名称
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param destFileName        目标文件名称
     * @param archiverName        压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void compressArchiver(String srcDirAbsolutePath, String srcFileName, String destDirAbsolutePath, String destFileName, String archiverName) throws IOException, ArchiveException {
        if (StringUtils.isBlank(srcDirAbsolutePath)) {
            throw new IllegalArgumentException("Source Directory's absolute path must not be null.");
        } else if (StringUtils.isBlank(destDirAbsolutePath)) {
            throw new IllegalArgumentException("Destination Directory's absolute path must not be null.");
        }

        String srcFileAbsolutePath;
        if (StringUtils.isBlank(srcFileName)) {
            srcFileAbsolutePath = srcDirAbsolutePath;
        } else {
            srcFileAbsolutePath = StringUtils.join(srcDirAbsolutePath, "/", srcFileName);
        }

        String destFileAbsolutePath;
        String archiverExtensionName = CompressorFactory.archiversMap.get(archiverName);
        if (StringUtils.isBlank(destFileName)) {
            destFileAbsolutePath = StringUtils.join(destDirAbsolutePath, "/", FilenameUtils.getBaseName(srcFileAbsolutePath), ".", archiverExtensionName);
        } else {
            //校验文件名是否与压缩格式冲突
            String destFileExtensionName = FilenameUtils.getExtension(destFileName);
            if (!destFileExtensionName.equals(archiverExtensionName)) {
                throw new IllegalArgumentException("Archiver file's extenstion name is " + archiverExtensionName + " but destination file's extenstion name is " + destFileExtensionName);
            }
            destFileAbsolutePath = StringUtils.join(destDirAbsolutePath, "/", destFileName);
        }
        File srcFile = FileUtils.getFile(srcFileAbsolutePath);
        File destFile = FileUtils.getFile(destFileAbsolutePath);
        if (!srcFile.isDirectory()) {
            this.compressFileByArchiver(srcFile, destFile, archiverName);
        } else {
            this.compressDirectoryByArchiver(srcFile, destFile, archiverName);
        }
    }

    /**
     * 压缩文件
     *
     * @param srcFile      源文件
     * @param destFile     目标文件
     * @param archiverName 压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void compressFileByArchiver(File srcFile, File destFile, String archiverName) throws IOException, ArchiveException {
        if (srcFile.isDirectory()) {
            throw new IllegalArgumentException("Source file is directory");
        }
        if (destFile.isDirectory()) {
            throw new IllegalArgumentException("Destination file is directory");
        }
        FileOutputStream fos = FileUtils.openOutputStream(destFile);
        ArchiveOutputStream aos = new ArchiveStreamFactory().createArchiveOutputStream(archiverName.toLowerCase(Locale.ENGLISH), fos);
        this.appendFileToArchive(srcFile, aos);
        aos.finish();
        IOUtils.closeQuietly(aos);
    }

    /**
     * 压缩多个文件
     *
     * @param srcFileList  源文件
     * @param destFile     目标文件
     * @param archiverName 压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void compressFileListByArchiver(List<File> srcFileList, File destFile, String archiverName) throws IOException, ArchiveException {
        if (destFile.isDirectory()) {
            throw new IllegalArgumentException("Destination file is directory");
        }
        FileOutputStream fos = FileUtils.openOutputStream(destFile);
        ArchiveOutputStream aos = new ArchiveStreamFactory().createArchiveOutputStream(archiverName.toLowerCase(Locale.ENGLISH), fos);
        for (File srcFile : srcFileList) {
            if (srcFile.isDirectory()) {
                throw new IllegalArgumentException("Source file is directory");
            }
            this.appendFileToArchive(srcFile, aos);
        }
        aos.finish();
        IOUtils.closeQuietly(aos);
    }

    /**
     * 压缩文件夹
     *
     * @param srcFile      源文件夹
     * @param destFile     目标文件
     * @param archiverName 压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void compressDirectoryByArchiver(File srcFile, File destFile, String archiverName) throws IOException, ArchiveException {
        if (!srcFile.isDirectory()) {
            throw new IllegalArgumentException("Source file is not directory");
        }
        if (destFile.isDirectory()) {
            throw new IllegalArgumentException("Destination file is directory");
        }
        FileOutputStream fos = FileUtils.openOutputStream(destFile);
        ArchiveOutputStream aos = new ArchiveStreamFactory().createArchiveOutputStream(archiverName.toLowerCase(Locale.ENGLISH), fos);
        if (srcFile.getParentFile().exists()) {
            this.appendFileToArchiveRecursion(srcFile.getParentFile().getAbsolutePath(), srcFile, aos);
        } else {
            this.appendFileToArchiveRecursion(srcFile.getAbsolutePath(), srcFile, aos);
        }
        aos.finish();
        IOUtils.closeQuietly(aos);
    }

    /**
     * 压缩多个文件夹
     *
     * @param srcFileList  源文件夹
     * @param destFile     目标文件
     * @param archiverName 压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void compressDirectoryListByArchiver(List<File> srcFileList, File destFile, String archiverName) throws IOException, ArchiveException {
        if (destFile.isDirectory()) {
            throw new IllegalArgumentException("Destination file is directory");
        }
        FileOutputStream fos = FileUtils.openOutputStream(destFile);
        ArchiveOutputStream aos = new ArchiveStreamFactory().createArchiveOutputStream(archiverName.toLowerCase(Locale.ENGLISH), fos);
        for (File srcFile : srcFileList) {
            if (!srcFile.isDirectory()) {
                throw new IllegalArgumentException("Source file is not directory");
            }
            this.appendFileToArchiveRecursion(srcFile.getAbsolutePath(), srcFile, aos);
        }
        aos.finish();
        IOUtils.closeQuietly(aos);
    }
    /*=========压缩文件 end=========*/

    /*=========解压文件 start=========*/

    /**
     * 向压缩文件流中添加文件
     *
     * @param srcFile 源文件
     * @param aos     压缩文件流
     * @throws IOException
     */
    private void appendFileToArchive(File srcFile, ArchiveOutputStream aos) throws IOException {
        ArchiveEntry entry = aos.createArchiveEntry(srcFile, srcFile.getName());
        if (srcFile == null) {
            throw new NullPointerException("Source must not be null");
        }
        if (srcFile.isDirectory()) {
            throw new IllegalArgumentException("Source file is directory");
        } else if (srcFile.isFile()) {
            aos.putArchiveEntry(entry);
            FileInputStream fis = FileUtils.openInputStream(srcFile);
            IOUtils.copy(fis, aos);
            aos.closeArchiveEntry();
            IOUtils.closeQuietly(fis);
        }
    }

    /**
     * 递归向压缩文件流中添加文件夹及文件
     *
     * @param rootPath 根路径
     * @param srcFile  源文件(夹)
     * @param aos      压缩文件流
     * @throws IOException
     */
    private void appendFileToArchiveRecursion(String rootPath, File srcFile, ArchiveOutputStream aos) throws IOException {
        if (srcFile == null) {
            throw new NullPointerException("Source must not be null");
        }
        String relativePath = srcFile.getAbsolutePath().substring(rootPath.length() + 1);
        ArchiveEntry entry = aos.createArchiveEntry(srcFile, relativePath);
        if (srcFile.isDirectory()) {
            File[] children = srcFile.listFiles();
            for (int i = 0; i < children.length; i++) {
                this.appendFileToArchiveRecursion(rootPath, children[i], aos);
            }
            if (entry.getSize() > 0) {
                aos.putArchiveEntry(entry);
                aos.closeArchiveEntry();
            }
        } else if (srcFile.isFile()) {
            aos.putArchiveEntry(entry);
            FileInputStream fis = FileUtils.openInputStream(srcFile);
            IOUtils.copy(fis, aos);
            aos.closeArchiveEntry();
            IOUtils.closeQuietly(fis);
        }
    }

    /**
     * 压缩文件
     *
     * @param srcDirAbsolutePath  源文件夹绝对路径
     * @param srcFileName         源文件名称
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param destFileName        目标文件名称
     * @param compressName        压缩类型:bzip2,gz,pack200,xz,lzma
     * @throws IOException
     * @throws CompressorException
     */
    public void compressCompressor(String srcDirAbsolutePath, String srcFileName, String destDirAbsolutePath, String destFileName, String compressName) throws IOException, CompressorException {
        if (StringUtils.isBlank(srcDirAbsolutePath)) {
            throw new IllegalArgumentException("Source Directory's absolute path must not be null.");
        } else if (StringUtils.isBlank(destDirAbsolutePath)) {
            throw new IllegalArgumentException("Destination Directory's absolute path must not be null.");
        }

        if (StringUtils.isBlank(srcFileName)) {
            throw new IllegalArgumentException("Source File's name must not be null.");
        }
        String srcFileAbsolutePath = StringUtils.join(srcDirAbsolutePath, "/", srcFileName);

        String destFileAbsolutePath;
        String compressExtensionName = CompressorFactory.compressorsMap.get(compressName);
        if (StringUtils.isBlank(destFileName)) {
            destFileAbsolutePath = StringUtils.join(destDirAbsolutePath, "/", FilenameUtils.getBaseName(srcFileAbsolutePath), ".", compressExtensionName);
        } else {
            //校验文件名是否与压缩格式冲突
            String destFileExtensionName = FilenameUtils.getExtension(destFileName);
            if (!destFileExtensionName.equals(compressExtensionName)) {
                throw new IllegalArgumentException("Compress File's extenstion name is " + compressExtensionName + " but destination file's extenstion name is " + destFileExtensionName);
            }
            destFileAbsolutePath = StringUtils.join(destDirAbsolutePath, "/", destFileName);
        }
        this.compressFileByCompressor(FileUtils.getFile(srcFileAbsolutePath), FileUtils.getFile(destFileAbsolutePath), compressName);
    }

    /**
     * 压缩文件
     *
     * @param srcFile      源文件
     * @param destFile     目标文件
     * @param compressName 压缩类型:bzip2,gz,pack200,xz,lzma
     * @throws IOException
     * @throws CompressorException
     */
    public void compressFileByCompressor(File srcFile, File destFile, String compressName) throws IOException, CompressorException {
        if (destFile == null) {
            throw new NullPointerException("Destination must not be null");
        }
        FileOutputStream fos = FileUtils.openOutputStream(destFile);
        this.compressFileByCompressor(srcFile, fos, compressName);
        IOUtils.closeQuietly(fos);
    }

    /**
     * 压缩文件
     *
     * @param srcFile      源文件
     * @param os           目标流
     * @param compressName 压缩类型:bzip2,gz,pack200,xz,lzma
     * @throws IOException
     * @throws CompressorException
     */
    public void compressFileByCompressor(File srcFile, OutputStream os, String compressName) throws IOException, CompressorException {
        if (srcFile == null) {
            throw new NullPointerException("Source must not be null");
        }
        FileInputStream fis = FileUtils.openInputStream(srcFile);
        CompressorOutputStream cos = new CompressorStreamFactory().createCompressorOutputStream(compressName.toLowerCase(Locale.ENGLISH), os);
        IOUtils.copy(fis, cos);
        cos.close();
        IOUtils.closeQuietly(fis);
    }

    /**
     * @param srcDirAbsolutePath  源文件夹绝对路径
     * @param srcFileName         源文件名称
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param compressType        压缩类型：ar,arj,cpio,dump,jar,tar,zip;bzip2,gz,pack200,xz,lzma
     * @return String 解压后的文件全路径
     * @throws Exception
     */
    public String uncompress(String srcDirAbsolutePath, String srcFileName, String destDirAbsolutePath, String compressType) throws Exception {
        String srcFileAbsolutePath = StringUtils.join(srcDirAbsolutePath, "/", srcFileName);
        return uncompress(srcFileAbsolutePath, destDirAbsolutePath, compressType);
    }

    /**
     * 解压文件
     *
     * @param srcFileAbsolutePath 源文件绝对路径
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param compressType        压缩类型：ar,arj,cpio,dump,jar,tar,zip;bzip2,gz,pack200,xz,lzma
     * @return String 解压后的文件全路径
     * @throws Exception
     */
    public String uncompress(String srcFileAbsolutePath, String destDirAbsolutePath, String compressType) throws Exception {
        // zip
        // 在mac系统上压缩后的zip文件使用apache ArchiveStream 解压报错：unsupported feature data descriptor used
        // 因此换成ZipInputStream
        if (StringUtils.equalsIgnoreCase(ArchiveStreamFactory.ZIP, compressType.toLowerCase(Locale.ENGLISH))) {
            uncompressZip(srcFileAbsolutePath, destDirAbsolutePath);
            return null;
        }
        //ar,arj,cpio,dump,jar,tar,zip
        if (CompressorFactory.archiversMap.containsKey(compressType.toLowerCase(Locale.ENGLISH))) {
            uncompressArchiver(srcFileAbsolutePath, destDirAbsolutePath, compressType);
            return null;
        }
        //bzip2,gz,pack200,xz,lzma
        else if (CompressorFactory.compressorsMap.containsKey(compressType.toLowerCase(Locale.ENGLISH))) {
            return uncompressCompressor(srcFileAbsolutePath, destDirAbsolutePath, compressType);
        }
        //7z
        else if (CompressorFactory.othersMap.containsKey(compressType.toLowerCase(Locale.ENGLISH))) {
            throw new IllegalArgumentException("Compressor or archiver " + compressType + " not support.");
        } else {
            throw new IllegalArgumentException("Compressor or archiver " + compressType + " not found.");
        }
    }

    /**
     * 解压zip文件
     *
     * @param srcFileAbsolutePath 源文件绝对路径
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @throws IOException
     * @throws ArchiveException
     */
    public void uncompressZip(String srcFileAbsolutePath, String destDirAbsolutePath) throws IOException, ArchiveException {
        if (StringUtils.isBlank(srcFileAbsolutePath)) {
            throw new IllegalArgumentException("Source's absolute path must not be null.");
        }
        uncompressZip(FileUtils.getFile(srcFileAbsolutePath), destDirAbsolutePath);
    }

    /**
     * 解压zip文件
     *
     * @param srcFile             源文件
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @throws IOException
     * @throws ArchiveException
     */
    public void uncompressZip(File srcFile, String destDirAbsolutePath) throws IOException, ArchiveException {
        if (StringUtils.isBlank(destDirAbsolutePath)) {
            throw new IllegalArgumentException("Destination's absolute path must not be null.");
        }
        uncompressZip(srcFile, FileUtils.getFile(destDirAbsolutePath));
    }

    /**
     * 解压zip文件
     *
     * @param srcFile 源文件
     * @param destDir 目标文件夹
     * @throws IOException
     * @throws ArchiveException
     */
    public void uncompressZip(File srcFile, File destDir) throws IOException, ArchiveException {
        if (srcFile == null) {
            throw new NullPointerException("Source must not be null");
        }
        if (destDir == null) {
            throw new NullPointerException("Destination must not be null");
        }
        ZipFile zipFile = new ZipFile(srcFile);
        Enumeration<?> entries = zipFile.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = (ZipEntry) entries.nextElement();
            if (!entry.isDirectory()) {
                File targetFile = FileUtils.getFile(destDir.getAbsolutePath() + "/" + entry.getName());
                // 保证这个文件的父文件夹必须要存在
                if (!targetFile.getParentFile().exists()) {
                    targetFile.getParentFile().mkdirs();
                }
                targetFile.createNewFile();
                // 将压缩文件内容写入到这个文件中
                InputStream is = zipFile.getInputStream(entry);
                FileOutputStream fos = FileUtils.openOutputStream(targetFile);
                IOUtils.copy(is, fos);
                // 关流顺序
                IOUtils.closeQuietly(fos, is);
            }
        }
    }

    /**
     * 解压文件
     *
     * @param srcFileAbsolutePath 源文件绝对路径
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param archiverName        压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void uncompressArchiver(String srcFileAbsolutePath, String destDirAbsolutePath, String archiverName) throws IOException, ArchiveException {
        if (StringUtils.isBlank(srcFileAbsolutePath)) {
            throw new IllegalArgumentException("Source's absolute path must not be null.");
        }
        uncompressArchiver(FileUtils.getFile(srcFileAbsolutePath), destDirAbsolutePath, archiverName);
    }
    /*=========解压文件 end=========*/

    /*=========重命名文件start=========*/

    /**
     * 解压文件
     *
     * @param srcFile             源文件
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param archiverName        压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void uncompressArchiver(File srcFile, String destDirAbsolutePath, String archiverName) throws IOException, ArchiveException {
        if (StringUtils.isBlank(destDirAbsolutePath)) {
            throw new IllegalArgumentException("Destination's absolute path must not be null.");
        }
        uncompressArchiver(srcFile, FileUtils.getFile(destDirAbsolutePath), archiverName);
    }

    /**
     * 解压文件
     *
     * @param srcFile      源文件
     * @param destDir      目标文件夹
     * @param archiverName 压缩类型：ar,arj,cpio,dump,jar,tar,zip
     * @throws IOException
     * @throws ArchiveException
     */
    public void uncompressArchiver(File srcFile, File destDir, String archiverName) throws IOException, ArchiveException {
        if (srcFile == null) {
            throw new NullPointerException("Source must not be null");
        }
        if (destDir == null) {
            throw new NullPointerException("Destination must not be null");
        }
        FileInputStream fis = FileUtils.openInputStream(srcFile);
        ArchiveInputStream ais = new ArchiveStreamFactory().createArchiveInputStream(archiverName.toLowerCase(Locale.ENGLISH), fis);

        ArchiveEntry entry = null;
        while (null != (entry = ais.getNextEntry())) {
            File entryFile = FileUtils.getFile(StringUtils.join(destDir.getAbsolutePath(), "/", entry.getName()));
            if (!entry.isDirectory()) {
                FileOutputStream fos = FileUtils.openOutputStream(entryFile);
                if (ais.canReadEntryData(entry)) {
                    IOUtils.copy(ais, fos);
                }
                IOUtils.closeQuietly(fos);
            }
        }
        IOUtils.closeQuietly(ais, fis);
    }

    /*=========检查文件 start=========*/

    /**
     * 解压文件
     *
     * @param srcFileAbsolutePath 源文件绝对路径
     * @param destDirAbsolutePath 目标文件夹绝对路径
     * @param compressName        压缩类型:bzip2,gz,pack200,xz,lzma
     * @return destFileName 解压后的文件名
     * @throws IOException
     * @throws CompressorException
     */
    public String uncompressCompressor(String srcFileAbsolutePath, String destDirAbsolutePath, String compressName) throws IOException, CompressorException {
        if (StringUtils.isBlank(srcFileAbsolutePath)) {
            throw new IllegalArgumentException("Source's absolute path must not be null.");
        }
        String destFileName = null;
        if (CompressorStreamFactory.GZIP.equalsIgnoreCase(compressName.toLowerCase(Locale.ENGLISH))) {
            destFileName = GzipUtils.getUncompressedFilename(srcFileAbsolutePath);
        } else if (CompressorStreamFactory.BZIP2.equalsIgnoreCase(compressName.toLowerCase(Locale.ENGLISH))) {
//            org.apache.commons.compress.compressors.bzip2.BZip2Utils line35
            //uncompressSuffix should put <".bzip2","">
            String extension = FilenameUtils.getExtension(srcFileAbsolutePath).replace(CompressorStreamFactory.BZIP2, "bz2");
            String baseName = FilenameUtils.getBaseName(srcFileAbsolutePath);
            destFileName = BZip2Utils.getUncompressedFilename(baseName + "." + extension);
        } else if (CompressorStreamFactory.XZ.equalsIgnoreCase(compressName.toLowerCase(Locale.ENGLISH))) {
            destFileName = XZUtils.getUncompressedFilename(srcFileAbsolutePath);
        } else if (CompressorStreamFactory.LZMA.equalsIgnoreCase(compressName.toLowerCase(Locale.ENGLISH))) {
            //暂未实现
            throw new IllegalArgumentException("Compressor or archiver " + compressName + " not support.");
        } else if (CompressorStreamFactory.PACK200.equalsIgnoreCase(compressName.toLowerCase(Locale.ENGLISH))) {
            //暂未实现
            throw new IllegalArgumentException("Compressor or archiver " + compressName + " not support.");
        }
        String destFileAbsolutePath = StringUtils.join(destDirAbsolutePath, "/", destFileName);
        uncompressCompressor(FileUtils.getFile(srcFileAbsolutePath), destFileAbsolutePath, compressName);
        return destFileName;
    }
    /*=========检查文件 end=========*/

    /**
     * 解压文件
     *
     * @param srcFile              源文件
     * @param destFileAbsolutePath 目标文件绝对路径
     * @param compressName         压缩类型:bzip2,gz,pack200,xz,lzma
     * @throws Exception
     * @throws CompressorException
     */
    public void uncompressCompressor(File srcFile, String destFileAbsolutePath, String compressName) throws IOException, CompressorException {
        if (StringUtils.isBlank(destFileAbsolutePath)) {
            throw new IllegalArgumentException("Destination's absolute path must not be null.");
        }
        uncompressCompressor(srcFile, FileUtils.getFile(destFileAbsolutePath), compressName);
    }

    /**
     * 解压文件
     *
     * @param srcFile      源文件
     * @param destFile     目标文件
     * @param compressName 压缩类型:bzip2,gz,pack200,xz,lzma
     * @throws Exception
     * @throws CompressorException
     */
    public void uncompressCompressor(File srcFile, File destFile, String compressName) throws IOException, CompressorException {
        if (srcFile == null) {
            throw new NullPointerException("Source must not be null");
        }
        if (destFile == null) {
            throw new NullPointerException("Destination must not be null");
        }
        FileInputStream fis = FileUtils.openInputStream(srcFile);
        FileOutputStream fos = FileUtils.openOutputStream(destFile);
        CompressorInputStream cis = new CompressorStreamFactory().createCompressorInputStream(compressName.toLowerCase(Locale.ENGLISH), fis);
        IOUtils.copy(cis, fos);
        IOUtils.closeQuietly(cis, fis, fos);
    }

    /**
     * 重命名文件
     *
     * @param srcFileAbsolutePath  原文件绝对路径
     * @param destFileAbsolutePath 新文件绝对路径
     */
    public void renameFile(String srcFileAbsolutePath, String destFileAbsolutePath) {
        if (StringUtils.isBlank(srcFileAbsolutePath)) {
            throw new IllegalArgumentException("Source's absolute path must not be null.");
        }
        if (StringUtils.isBlank(destFileAbsolutePath)) {
            throw new IllegalArgumentException("Destination's absolute path must not be null.");
        }
        renameFile(FileUtils.getFile(srcFileAbsolutePath), FileUtils.getFile(destFileAbsolutePath));
    }

    /**
     * 重命名文件
     *
     * @param srcFile  原文件名文件对象
     * @param destFile 新文件名文件对象
     */
    private void renameFile(File srcFile, File destFile) {
        if (srcFile == null) {
            throw new NullPointerException("Source must not be null");
        }
        if (destFile == null) {
            throw new NullPointerException("Destination must not be null");
        }
        srcFile.renameTo(destFile);
    }

    /**
     * 检查源文件(夹)与目标文件(夹)是否相同
     *
     * @param src  源文件(夹)
     * @param dest 目标文件(夹)
     * @throws IOException
     */
    public void checkSrcEqualDest(File src, File dest) throws IOException {
        if (src.getCanonicalPath().equals(dest.getCanonicalPath())) {
            throw new IOException("Source '" + src.getAbsolutePath() + "' and destination '" + dest.getAbsolutePath() + "' are the same");
        }
    }

    /**
     * 获取文件名的最后一段
     * <pre>
     * 	C:/abc/sz/abc.sz 			-->abc.sz
     *  //server/d/abc.sz.tar.gz 	-->abc.sz.tar.gz
     *  abc							-->abc
     *  C:/abd/						-->""
     * </pre>
     *
     * @param filename
     * @return
     */
    public String getLastFilename(String filename) {
        if (null == filename) {
            return null;
        }
        if (filename.endsWith("/") || filename.endsWith("\\")) {
            return "";
        }
        int index = filename.lastIndexOf('/');
        if (-1 == index) {
            index = filename.lastIndexOf('\\');
        }
        return -1 == index ? filename : filename.substring(index + 1);
    }

    /**
     * 获取文件大小
     *
     * @param file
     * @return
     * @throws IOException
     */
    public String getFileSize(File file) throws IOException {
        double fileSize = file.length();
        String fileSizeString = "0.00B";
        DecimalFormat df = new DecimalFormat("#0.00");
        if (fileSize < SIZE_RADIX_K) {
            fileSizeString = df.format(fileSize) + SIZE_MEASUREMENT_UNIT_B;
        } else if (fileSize < SIZE_RADIX_M) {
            fileSizeString = df.format(fileSize / SIZE_RADIX_K) + SIZE_MEASUREMENT_UNIT_K;
        } else if (fileSize < SIZE_RADIX_G) {
            fileSizeString = df.format(fileSize / SIZE_RADIX_M) + SIZE_MEASUREMENT_UNIT_M;
        } else if (fileSize < SIZE_RADIX_T) {
            fileSizeString = df.format(fileSize / SIZE_RADIX_G) + SIZE_MEASUREMENT_UNIT_G;
        } else {
            fileSizeString = df.format(fileSize / SIZE_RADIX_T) + SIZE_MEASUREMENT_UNIT_T;
        }
        return fileSizeString;
    }

    public static class CompressorFactory {
        public static final Map<String, String> archiversMap = new HashMap<String, String>();
        public static final Map<String, String> compressorsMap = new HashMap<String, String>();
        public static final Map<String, String> othersMap = new HashMap<String, String>();
        public static final Map<String, String> compressorFactoryMap = new HashMap<String, String>();

        static {
            archiversMap.put(ArchiveStreamFactory.AR, "ar");
            archiversMap.put(ArchiveStreamFactory.ARJ, "arj");
            archiversMap.put(ArchiveStreamFactory.CPIO, "cpio");
            archiversMap.put(ArchiveStreamFactory.DUMP, "dump");
            archiversMap.put(ArchiveStreamFactory.JAR, "jar");
            archiversMap.put(ArchiveStreamFactory.TAR, "tar");
            archiversMap.put(ArchiveStreamFactory.ZIP, "zip");

            othersMap.put("7z", "7z");
            othersMap.put("rar", "rar");

            compressorsMap.put(CompressorStreamFactory.BZIP2, "bz2");
            compressorsMap.put(CompressorStreamFactory.GZIP, "gz");
            compressorsMap.put(CompressorStreamFactory.PACK200, "pack200");
            compressorsMap.put(CompressorStreamFactory.XZ, "xz");
            compressorsMap.put(CompressorStreamFactory.LZMA, "lzma");

            compressorFactoryMap.putAll(archiversMap);
            compressorFactoryMap.putAll(compressorsMap);
            compressorFactoryMap.putAll(othersMap);
        }
    }
}

